package Utils;

/**
 *  A Squid object is able to manage a huge amount files in a reasonable
 *  way. It operates inside a specified directory. Files stored in this
 *  directory are identified with a long number. File paths are computed
 *  from this unique identification by a special algorithm, translating
 *  numbers into strings. Files are not stored directly in the specified
 *  directory. Instead, subdirectories are created that in turn contain
 *  files. Both the subdirectories' names and files' names depend on the
 *  indentification values.
 *
 * @author    Leo Galambos
 */
public class Squid {
    /**
     *  System-dependent separator of directories.
     */
    final static String FS = System.getProperty("file.separator");

    /**
     *  The maximum length of a file name. All positive long values can be
     *  encoded into a string of this length. This length will later be
     *  split into two parts: a directory part and a file part.
     */
    final static int MAX_LEN = Long.toString(Long.MAX_VALUE, Character.MAX_RADIX).length();

    /**
     *  The length of file names. All files will have the same length. The
     *  rest of MAX_LEN (MAX_LEN&nbsp;-&nbsp;FILE_LEN) will be used for
     *  directory names. This value must be less than MAX_LEN and must not
     *  be equal to 3 on OS/2 platform because of a potential problem with
     *  CON and NUL devices.
     */
    final static int FILE_LEN = 3;

    /**
     *  A shared array of characters that is modified during append().
     */
    final static char[] zeroS = zero0();

    /**
     *  The base directory inside which this instance of Squid operates.
     */
    String directory;

    /**
     *  Creates a new Squid object that will operate inside the specified
     *  directory.
     *
     * @param  directory  The directory inside which the squid will
     *      operate.
     */
    public Squid(String directory) {
        this.directory = directory;
    }

    // constructor

    /**
     *  A test method that takes 1.000 different long values and encodes
     *  them into their respective file paths. The paths are printed out to
     *  the console without storing anything on disk. All paths start with
     *  "./my/dir".
     *
     * @param  args           Command line arguments, ignored.
     * @exception  Throwable  If anything goes wrong, an exception could be
     *      thrown out.
     */
    public static void main (String args[]) throws Throwable {
        Squid squid = new Squid("." + FS + "my" + FS + "dir");
        for (long l = 0; l < 1000000; l += 1000) {
            System.out.println(squid.prepare(l));
        }
        // for l
    }

    // main

    /**
     *  Description of the Method
     *
     * @param  directory  Description of the Parameter
     * @param  file       Description of the Parameter
     * @return            Description of the Return Value
     */
    public static String prepare(String directory, long[] file) {
        StringBuffer sb = new StringBuffer(directory);
        for (int i = 0; i < file.length; i++) {
            sb.append(FS);
            append(sb, file[i], FILE_LEN);
        }
        return sb.toString();
    }

    /**
     *  Description of the Method
     *
     * @param  directory  Description of the Parameter
     * @param  a          Description of the Parameter
     * @param  b          Description of the Parameter
     * @return            Description of the Return Value
     */
    public static String prepare(String directory, long a, long b) {
        StringBuffer sb = new StringBuffer(directory);
        sb.append(FS);
        append(sb, a, FILE_LEN);
        sb.append(FS);
        append(sb, b, FILE_LEN);
        return sb.toString();
    }

    /**
     *  Description of the Method
     *
     * @param  directory  Description of the Parameter
     * @param  file       Description of the Parameter
     * @return            Description of the Return Value
     */
    public static String prepare(String directory, long file) {
        StringBuffer sb = new StringBuffer(directory);
        sb.append(FS);
        append(sb, file, FILE_LEN);
        return sb.toString();
    }

    /**
     *  Description of the Method
     *
     * @param  file  Description of the Parameter
     * @return       Description of the Return Value
     */
    public String prepare(long file) {
        return prepare(directory, file);
    }

    /**
     *  Description of the Method
     *
     * @param  a  Description of the Parameter
     * @param  b  Description of the Parameter
     * @return    Description of the Return Value
     */
    public String prepare(long a, long b) {
        return prepare(directory, a, b);
    }

    /**
     *  Description of the Method
     *
     * @param  file  Description of the Parameter
     * @return       Description of the Return Value
     */
    public String prepare(long[] file) {
        return prepare(directory, file);
    }

    /**
     *  Creates a new char array and fills it with zeros. The array is
     *  MAX_LEN positions long. Here, zero means Unicode character '0', not
     *  '\u0000'.
     *
     * @return    The newly created array of characters filled with zeros.
     */
    private static char[] zero0() {
        char[] zeroS = new char[MAX_LEN];

        for (int i = 0; i < MAX_LEN; i++) {
            zeroS[i] = '0';
        }

        return zeroS;
    }

    /**
     *  Adds the final part of a file's path (last dir + file name) to a
     *  string buffer. The files's name and the directory where it is
     *  stored are determined by the value.
     *
     * @param  sb             A string buffer that is supposed to contain
     *      the first part of path, common for all files, before this
     *      method is called.
     * @param  value          A long value from which the file's name will
     *      be determined.
     * @param  separator_pos  How long should be the file's name itself,
     *      without any preceding directory.
     * @return                The complete path to a file that corresponds
     *      to the specified long value.
     */
    private static StringBuffer append(StringBuffer sb, long value, int separator_pos) {
        String val = Long.toString(value, Character.MAX_RADIX);
        int zero = MAX_LEN - val.length();
        int beforeFS = MAX_LEN - separator_pos;

        // Is the first non-zero digit before the file separator?
        // If yes, a part of "val" must be added to the directory's name, the rest will be used
        // for the file's name.
        // An underscore is appended to the end to prevent conflicts with Windows and OS/2 devices
        // such as CON, PRN, NUL, LPTx, etc.
        if (zero < beforeFS) {
            sb.append(zeroS, 0, zero);
            beforeFS -= zero;
            char[] str = val.toCharArray();
            sb.append(str, 0, beforeFS);
            sb.append(FS);
            sb.append(str, beforeFS, str.length - beforeFS);
            sb.append("_");
        } else {
            sb.append(zeroS, 0, beforeFS);
            sb.append(FS);
            sb.append(zeroS, 0, zero - beforeFS);
            sb.append(val);
            sb.append("_");
        }

        return sb;
    }

}

